#include <stdio.h>
#include <math.h>
#include "rsa.h"

/**
 * @brief Generate the e parameter of the public key.
 *
 * e is a number, coprime to phi(n) between 1 and phi(n).
 * Choose e and store it in the public key.
 *
 * @param pub Public key
 * @param priv Private key
 *
 * @return 01 in case or error, 0 otherwise
 */
static int     choose_e(t_publicKey *pub, t_privateKey *priv)
{
    t_uint64   i;

    i = 65537;
    while (i < priv->phi_n)
        {
            if (gcd(i, priv->phi_n) == 1)
                {
                    pub->e = i;
                    return (0);
                }
            i++;
        }
    fprintf(stderr, "Impossible to choose 'e'\n");
    return (-1);
}

/**
 * @brief Generate the d parameter of the private key.
 *
 * d is the multiplicative inverse of e % phi(n).
 * Calculate d and store it in the private key.
 *
 * @param pub Public key
 * @param priv Private priv
 *
 * @return -1 in case of error, 0 otherwise
 */
static int    compute_d(t_publicKey *pub, t_privateKey *priv)
{
    priv->d = multiplicative_mod_inverse(pub->e, priv->phi_n);
    if (priv->d == 0)
        {
            fprintf(stderr, "Impossible to compute 'd'\n");
            return (-1);
        }
    return (0);
}

/**
 * @brief Check if the number is prime.
 *
 * Check all odd divisors lesser than square root of the number.
 *
 * @param nb Number to check
 *
 * @return 1 if the number is prime, 0 otherwise
 */
static unsigned int     is_prime(t_uint64 nb)
{
    t_uint64            limit;
    t_uint64            i;

    limit = sqrt(nb) + 1;
    if (nb % 2 == 0)
        return (0);
    for (i = 3 ; i < limit; i += 2)
        if (nb % i == 0)
            return (0);
    return (1);
}

/**
 * @brief Generate the private and public keys structures.
 *
 * If p or q is null, then generate random p and q. Check if they are both prime
 * Process n and phi(n) then select e and compute d.
 *
 * @param pub Public Key to fill
 * @param priv Private Key to fill
 * @param p First prime for RSA
 * @param q Second prime for RSA
 *
 * @return -1 if error, 0 otherwise
 */
int     generate_keys(t_publicKey *pub, t_privateKey *priv,
                      t_uint64 p, t_uint64 q)
{
    if (!p || !q)
        {
            if (generate_primes(priv) == -1)
                return (-1);
        }
    else if (!is_prime(p) || !is_prime(q))
        return (-1);
    else
        {
            priv->p = p;
            priv->q = q;
        }
    pub->n = priv->p * priv->q;
    priv->n = pub->n;
    priv->phi_n = (priv->p - 1) * (priv->q - 1);
    if (choose_e(pub, priv) == -1)
        return (-1);
    if (compute_d(pub, priv) == -1)
        return (-1);
    return (0);
}
